// /scripts/b.mjs/脚本/b.mjs
import fs from 'fs'
import http from 'http'
import https from 'https'
import path from 'path'
import URL from 'url'
import zlib from 'zlib'
import {
	apiOutDir,
	methodImport,
	outFileType,
	projectId,
	requestImport,
	requestOutDir,
	responseImport,
	responseOutDir,
	responseValueOutDir,
	tempDir,
	VITE_APP_API_PREFIX
} from './a.mjs'

export const typeTransform = {
	integer: 'number', object: 'any', array: 'any[]'
}

export function build(apiList, schemas) {
	let dataList = []
	// 组装数据
	if (apiList.length) {
		let requestBody = fs.existsSync(path.resolve(tempDir + 'request.body.json')) ? JSON.parse(fs.readFileSync(path.resolve(tempDir + 'request.body.json'), { encoding: 'utf-8' }) || '{}') : {}
		let responseBody = fs.existsSync(path.resolve(tempDir + 'response.body.json')) ? JSON.parse(fs.readFileSync(path.resolve(tempDir + 'response.body.json'), { encoding: 'utf-8' }) || '{}') : {}
		for (let item of apiList) {
			if (/head|options|patch/.test(item.method)) {
				continue
			}
			let url = item.path.replace(VITE_APP_API_PREFIX, '')
			let pureUrl = url.replace(/\/\{[^{}]+}/g, '/{x}')
			let pathReqUrl = url.replace(/\/\{/g, '/${')
			let key = createNameByUrl(VITE_APP_API_PREFIX, item.path, item.method)
			let req = item.requestBody.jsonSchema || {}
			let res = item.responses.find((i) => i.code === 200)?.jsonSchema || {
				type: 'object', properties: {}
			}
			let requestSchema = req.properties || findSchema(req.$ref, schemas)?.properties
			let responseSchema = res.properties?.data || findSchema(res.$ref, schemas)?.properties?.data || {} // aa |  169811563
			let requestList = []
			if (requestSchema) {
				if (req.required?.length) {
					req.required.forEach((i) => {
						requestSchema[i].required = true
					})
				}
				Object.keys(requestSchema)
				      .sort((a, b) => a.localeCompare(b))
				      .forEach((key) => {
					      if (requestSchema[key].type) {
						      if (Array.isArray(requestSchema[key].type)) {
							      let typeList = requestSchema[key].type.map((i) => typeTransform[i] || i)
							      requestSchema[key].type = [...new Set(typeList)].join(' | ')
						      }
						      requestList.push({
							      name: key, ...requestSchema[key]
						      })
					      }
				      })
			}
			if (responseSchema.$ref) {
				responseSchema.properties = findSchema(responseSchema.$ref, schemas).properties
			} else if (responseSchema.items?.$ref) {
				responseSchema.items = findSchema(responseSchema.items.$ref, schemas)
			}
			delete responseSchema.$ref
			delete responseSchema['x-apifox-orders']
			delete responseSchema.items?.['x-apifox-orders']
			delete responseSchema.items?.$ref
			let config = {
				url,
				pureUrl,
				pathReqUrl,
				method: item.method,
				apiName: item.name,
				key,
				fnName: getFnName(key),
				requestBaseName: 'I' + upperFirstCase(key),
				requestName: ['I' + upperFirstCase(key)],
				responseBaseName: 'O' + upperFirstCase(key),
				responseName: ['O' + upperFirstCase(key)],
				responseValueName: ['O' + upperFirstCase(key) + 'Value'],
				request: {
					// file
					contentType: item.requestBody.type,
					params: (item.requestBody.parameters || []).concat(requestList), // query/path
					query: (item.parameters.query || []).concat(item.commonParameters.query || []),
					path: (item.parameters.path || []).concat(item.commonParameters.path || []),
					cookie: (item.parameters.cookie || []).concat(item.commonParameters.cookie || []),
					header: (item.parameters.header || []).concat(item.commonParameters.header || [])
				},
				response: responseSchema
			}
			// 从地址中提取参数
			if (/\/\{[^{}]+}/.test(config.url)) {
				let otherPath = config.url.match(/\/\{([^{}\/]+)\}/g)
				otherPath.forEach((item) => {
					item = item.replace(/\/\{|}/g, '')
					let has = config.request.path.find((i) => i.name === item)
					if (!has) {
						config.request.path.push({
							name: item, type: 'number | string'
						})
					}
				})
			}
			let bodyKey = config.method.toLowerCase() + '_' + config.pureUrl
			// 处理前后端类型映射
			config = translateType(config, requestBody[bodyKey], responseBody[bodyKey])
			config = {
				bodyKey, ...config
			}
			let fnConfig = buildFn(config)
			let reqConfig = buildReq(config)
			let resConfig = buildRes(config)
			mergeObj(fnConfig)
			mergeObj(reqConfig)
			mergeObj(resConfig)
			// 删除确定不要的类型
			if (config.deleteResponseName) {
				let index = config.responseName.indexOf(config.deleteResponseName)
				config.responseName.splice(index, 1)
				let valueIndex = config.responseValueName.indexOf(config.deleteResponseName + 'Value')
				config.responseValueName.splice(valueIndex, 1)
			}
			if (config.deleteRequestName) {
				let index = config.requestName.indexOf(config.deleteRequestName)
				config.requestName.splice(index, 1)
			}

			function mergeObj(newObj) {
				for (let key in newObj) {
					let item = newObj[key]
					if (Array.isArray(item)) {
						if (config[key]) {
							item.forEach((s) => {
								if (!config[key].includes(s)) {
									config[key].push(s)
								}
							})
						} else {
							config[key] = item
						}
					} else if (typeof item === 'object') {
						for (let sk in item) {
							config[key][sk] = item[sk]
						}
					} else {
						config[key] = item
					}
				}
			}

			// 去重
			if (!dataList.find((i) => i.bodyKey === config.bodyKey)) {
				dataList.push(config)
			}
		}
	} else {
		return
	}
	dataList = dataList.sort((a, b) => a.key.localeCompare(b.key))
	fs.writeFileSync(path.resolve(tempDir + 'doc.json'), JSON.stringify(dataList, null, 4), { encoding: 'utf-8' })
	fs.writeFileSync(path.resolve(apiOutDir), fnCode(dataList, methodImport, requestImport, responseImport), { encoding: 'utf-8' })
	outFileType === 'ts' && fs.writeFileSync(path.resolve(requestOutDir), reqCode(dataList), { encoding: 'utf-8' })
	outFileType === 'ts' && fs.writeFileSync(path.resolve(responseOutDir), resCode(dataList), { encoding: 'utf-8' })
	outFileType === 'ts' && fs.writeFileSync(path.resolve(responseValueOutDir), resValueCode(dataList), {
		encoding: 'utf-8'
	})
}

export async function get({ name, Authorization, projectId, saveCache, filter, sort }) {
	let res = await request({
		url: `https://api.apifox.cn/api/v1/api-${name}?locale=zh-CN`, headers: {
			Authorization, Referer: 'https://www.apifox.cn/', 'x-project-id': projectId
		}, method: 'GET'
	})
	let data = JSON.parse(res)
	data = data.data
	if (saveCache && data) {
		// 缓存原始接口数据
		if (filter) {
			data = data.filter(filter)
		}
		if (sort) {
			data = data.sort(sort)
		}
		fs.writeFileSync(path.resolve(`${tempDir}${name}-old.json`), JSON.stringify(data, null, 4), { encoding: 'utf-8' })
	}
	return data
}

export async function request({ url, method, dataStr, headers }) {
	const urlObj = URL.parse(url)
	const protocol = urlObj.protocol.replace(':', '')
	console.log('正在获取接口数据...')
	return new Promise((resolve) => {
		const req = (protocol === 'https' ? https : http).request({
			...urlObj, ...(protocol === 'https' ? { rejectUnauthorized: false } : {}), method, // 请求方式
			headers: {
				...(method !== 'GET' ? { 'Content-Length': Buffer.byteLength(dataStr) } : {}), // post必须填写
				'Content-type': 'application/x-www-form-urlencoded', // 编码格式
				referer: url, // 如果资源有防盗链，则清空该属性
				...headers
			}
		}, (res) => {
			if (res.statusCode === 403) {
				console.log('请求状态：', 403, '错误', '\n排查以下原因：\n1.token是否存在或已失效\n2.是否有权限查看项目', projectId, '的权限\n项目地址：https://app.apifox.com/project/' + projectId)
			} else {
				console.log('已取得接口文档数据', res.statusCode, res.statusMessage)
			}
			// 给浏览器返回数据
			if (res.statusCode === 200) {
				// 设置编码格式
				res.setEncoding('utf-8')
				let data = ''
				res.on('data', (chunk) => {
					data += chunk
				})
				res.on('end', () => {
					resolve(data)
				})
			} else {
				resolve(JSON.stringify({
					code: res.statusCode, msg: res.statusMessage, data: null
				}))
			}
		})
		req.on('error', (e) => {
			console.log('error', e.toString())
			resolve(JSON.stringify({
				code: '500', msg: e.toString(), data: null
			}))
		})
		if (method !== 'GET' && method !== 'DELETE') {
			// 向接口服务器 发送数据
			req.write(dataStr)
		}
		// 关闭发送通道
		req.end()
	})
}

export function upperFirstCase(str) {
	return str.substring(0, 1).toUpperCase() + str.substring(1)
}

export function getFnName(str) {
	return str.split(/-+/).map((i, index) => index === 0 ? i : upperFirstCase(i)).join('')
}

export function createNameByUrl(prefix, url, method) {
	let list = url
	.replace(prefix + '/', '')
	.replace(/[{}]/g, '')
	.split('/')
	list.push(method)

	return  list.filter(i => !!i).map((i, index) => index === 0 ? i : upperFirstCase(i)).join('')
}

function isStringType(key) {
	return /email|phone|name|avatar|url|title/i.test(key) || /\w+(No|Time|Title|By|Name)$/.test(key)
}

export function clearResponseData(data) {
	if (data === null) {
		return 'number | string'
	} else if (Array.isArray(data)) {
		if (data.length === 0) {
			return 'any[]'
		}
		let trueData = data.filter((i) => i !== null)
		if (trueData.length === 0 && data.length > 0) {
			trueData = [data[0]]
		}
		if (typeof trueData[0] === 'object') {
			let valueMap = {}
			data.forEach((item) => {
				Object.keys(item)
				      .sort((a, b) => a.localeCompare(b))
				      .forEach((key) => {
					      if (valueMap[key] === 'any' || !valueMap[key]) {
						      valueMap[key] =
							      item[key] === null && isStringType(key)
								      ? 'string'
								      : clearResponseData(item[key])
					      }
				      })
			})
			return [valueMap]
		} else if (Array.isArray(trueData[0])) {
			return 'any[][]'
		} else {
			return typeof trueData[0] + '[]'
		}
	} else if (typeof data === 'object') {
		let valueMap = {}
		Object.keys(data)
		      .sort((a, b) => a.localeCompare(b))
		      .forEach((key) => {
			      valueMap[key] = data[key] === null && isStringType(key) ? 'string' : clearResponseData(data[key])
		      })
		return valueMap
	} else {
		return typeof data
	}
}

export function findSchema(ref, schemas) {
	let result = {
		properties: {}
	}
	if (ref) {
		let id = ref.match(/\d+/)?.[0] * 1
		let item = schemas.find((i) => i.id === id)
		if (item) {
			if (item.jsonSchema?.required?.length) {
				item.jsonSchema.required.forEach((i) => {
					item.jsonSchema.properties[i].required = true
				})
			}
			result = {
				properties: item.jsonSchema?.properties || {}
			}
		}
	}
	return result
}

export function buildFn(config) {
	let url = `\`${config.pathReqUrl}\``
	let hasFile = config.request.params.find((i) => i.type === 'file')
	let headers = `${config.request && config.request.contentType !== 'none' && !config.request.contentType.includes('form-data') ? `headers: { 'Content-Type': '${config.request.contentType}'}` : ''}`
	if (config.request.path.length) {
		let { requestName, responseName, code } = buildFnPathCode({
			url: url,
			fnName: config.fnName,
			requestName: config.requestName,
			requestBaseName: config.requestBaseName,
			responseBaseName: config.responseBaseName,
			method: config.method,
			apiName: config.apiName,
			headers,
			pathList: config.request.path,
			query: config.request.query,
			params: config.request.params,
			resProps: config.response
		})
		return {
			requestName, responseName, fnCode: code
		}
	} else if (hasFile) {
		let { requestName, responseName, code } = buildFnFileCode({
			url: url,
			fnName: config.fnName,
			requestBaseName: config.requestBaseName,
			responseBaseName: config.responseBaseName,
			method: config.method,
			apiName: config.apiName,
			headers,
			params: config.request.params,
			responseOldType: config.response.oldType
		})
		return {
			requestName, responseName, fnCode: code
		}
	} else {
		if (config.response.oldType === 'array') {
			let { requestName, responseName, code } = buildFnArrayCode({
				url: url,
				fnName: config.fnName,
				requestBaseName: config.requestBaseName,
				responseBaseName: config.responseBaseName,
				method: config.method,
				apiName: config.apiName,
				headers,
				params: config.request.params.concat(config.request.query)
			})
			return {
				requestName, responseName, fnCode: code
			}
		} else {
			let { requestName, responseName, code } = buildFnGetOrPostCode({
				url: url,
				fnName: config.fnName,
				requestBaseName: config.requestBaseName,
				responseBaseName: config.responseBaseName,
				method: config.method,
				apiName: config.apiName,
				headers,
				params: config.request.params.concat(config.request.query),
				resProps: config.response
			})
			return {
				requestName, responseName, fnCode: code
			}
		}
	}
}

export function buildFnPathCode(props) {
	const {
		url, fnName, requestBaseName, responseBaseName, method, apiName, headers, pathList, query, params, resProps
	} = props
	let pathStrList = []
	let remarkList = []
	let isBase = resProps.oldType !== 'array' && resProps.type !== 'any' && resProps.type !== responseBaseName
	let resName = isBase ? resProps.type : responseBaseName + `${resProps.oldType === 'array' ? 'Item' : ''}`
	let resNameValue = resName + `${resProps.oldType === 'array' ? '[]' : ''}`
	pathList.forEach((i) => {
		pathStrList.push(`${i.name}${i.required ? '' : '?'}${outFileType === 'ts' ? `: ${i.type}` : ''}`)
		remarkList.push(` * @path {${i.type}} ${i.name} ${i.required ? '必填' : '可选'}: ${i.description === i.name ? '' : i.description.replace(/\n+/g, '')}`)
	})
	query.forEach((i) => {
		remarkList.push(` * @query {${i.type}} ${i.name} ${i.required ? '必填' : '可选'}: ${i.description === i.name ? '' : i.description.replace(/\n+/g, '')}`)
	})
	params.forEach((i) => {
		remarkList.push(` * @param {${i.type}} ${i.name} ${i.required ? '必填' : '可选'}: ${i.description === i.name ? '' : i.description.replace(/\n+/g, '')}`)
	})
	return {
		requestName: query.length === 0 && params.length === 0 ? [] : [requestBaseName + 'Group'],
		responseName: isBase ? [] : [resName],
		code: query.length === 0 && params.length === 0 ? `
/**
 * @description ${apiName} buildFnPathCode${remarkList.length ? `\n${remarkList.join('\n')}` : ''}
 * @return Promise<${outFileType === 'ts' ? `${resNameValue}` : 'object'}>
 */
export const ${fnName} = async (${pathStrList.join(', ')}) => {
\treturn ${method === 'delete' ? 'del' : method}${outFileType === 'ts' ? `<${resNameValue}>` : ''}(${url}${headers ? `, {\n\t\t${headers}\n\t}` : ''})
}
`.trim() : `
/**
 * @description ${apiName} buildFnPathCode${remarkList.length ? `\n${remarkList.join('\n')}` : ''}
 * @return Promise<${outFileType === 'ts' ? `${resNameValue}` : 'object'}>
 */
export const ${fnName} = async ({ path, params }${outFileType === 'ts' ? `: ${requestBaseName}Group` : ''}) => {
\treturn ${method === 'delete' ? 'del' : method}${outFileType === 'ts' ? `<${resNameValue}>` : ''}(${url.replace(/\$\{/g, '${path.')},{\n\t\tparams\n\t})
}
`.trim()
	}
}

export function buildFnFileCode(props) {
	const { url, fnName, requestBaseName, responseBaseName, method, apiName, headers, params, responseOldType } = props
	const isArray = responseOldType === 'array'
	let remarkList = []
	params.forEach((i) => {
		remarkList.push(` * @param {${i.type}} params.${i.name} ${i.required ? '必填' : '可选'}: ${i.description === i.name ? '' : i.description.replace(/\n+/g, '')}`)
	})
	return {
		requestName: [requestBaseName + 'FormData'], responseName: [responseBaseName], code: `
/**
 * @description ${apiName} buildFnFileCode
 * ${remarkList.length ? `\n${remarkList.join('\n')}` : ''}
 * @return Promise<${outFileType === 'ts' ? `${isArray ? responseBaseName + 'Item[]' : responseBaseName}` : 'object'}>
 */
export const ${fnName} = async (params${outFileType === 'ts' ? `?: ${requestBaseName}FormData` : ''}) => {
\treturn ${method === 'delete' ? 'del' : method}${outFileType === 'ts' ? `<${isArray ? responseBaseName + 'Item[]' : responseBaseName}>` : ''}(${url}, {
\t\tparams${headers ? `,\n\t\t${headers}` : ''}
\t})
}`.trim()
	}
}

export function buildFnArrayCode(props) {
	const { url, fnName, requestBaseName, responseBaseName, method, apiName, headers, params } = props
	let resName = responseBaseName + 'Item'
	let remarkList = []
	params.forEach((i) => {
		remarkList.push(` * @param {${i.type}} params.${i.name} ${i.required ? '必填' : '可选'}: ${i.description === i.name ? '' : i.description.replace(/\n+/g, '')}`)
	})
	return {
		requestName: [requestBaseName], responseName: [resName], code: `
/**
 * @description ${apiName} buildFnArrayCode
 * ${remarkList.length ? `\n${remarkList.join('\n')}` : ''}
 * @return Promise<${outFileType === 'ts' ? `${resName}[]` : 'object'}>
 */
export const ${fnName} = async (params${outFileType === 'ts' ? `?: ${requestBaseName}` : ''}) => {
\treturn ${method === 'delete' ? 'del' : method}${outFileType === 'ts' ? `<${resName}[]>` : ''}(${url}, {
\t\tparams${headers ? `,\n\t\t${headers}` : ''}
\t})
}`.trim()
	}
}

export function buildFnGetOrPostCode(props) {
	const {
		url, fnName, requestBaseName, responseBaseName, method, apiName, headers, params, resProps
	} = props
	let remarkList = []
	let isBase = resProps.oldType !== 'array' && resProps.type !== 'any' && resProps.type !== responseBaseName
	let resName = isBase ? resProps.type : responseBaseName
	params.forEach((i) => {
		remarkList.push(` * @param {${i.type}} params.${i.name} ${i.required ? '必填' : '可选'}: ${i.description?.replace(/\n+/g, ' ') || ''}`)
	})
	return {
		requestName: [requestBaseName], responseName: isBase ? [] : [resName], code: `
/**
 * @description ${apiName} buildFnGetOrPostCode
 * ${remarkList.length ? `\n${remarkList.join('\n')}` : ''}
 * @return Promise<${outFileType === 'ts' ? resName : 'object'}>
 */
export const ${fnName} = async (params${outFileType === 'ts' ? `?: ${requestBaseName}` : ''}) => {
\treturn ${method === 'delete' ? 'del' : method}${outFileType === 'ts' ? `<${resName}>` : ''}(${url}, {
\t\tparams${headers ? `,\n\t\t${headers}` : ''}
\t})
}`.trim()
	}
}

export function buildReq(config) {
	let hasFile = config.request.params.find((i) => i.type === 'file')
	if (hasFile) {
		// 单独处理文件类型
		let { code, name, deleteName } = buildReqFileTyped({
			url: config.url, name: config.requestName, apiName: config.apiName, fileProps: config.request.params
		})
		return {
			requestName: name, deleteRequestName: deleteName, reqCode: code
		}
	} else {
		// 处理get/post请求的参数
		const { code, name } = buildReqGetOrPostTyped({
			url: config.url,
			name: config.requestBaseName,
			apiName: config.apiName,
			params: config.request.params.concat(config.request.query),
			path: config.request.path
		})
		return {
			requestName: name, reqCode: code
		}
	}
}

export function buildReqGetOrPostTyped(props) {
	const { url, name, apiName, params, path } = props
	const typeCode = []
	const pathCode = []
	params.forEach((i) => {
		typeCode.push(`${i.name}${i.required ? '' : '?'}: ${typeof i.type === 'string' ? i.type : 'any'};${i.description ? ` // ${i.description.replace(/\n+/g, '')}` : ''}`)
	})
	path.forEach((i) => {
		pathCode.push(`${i.name}${i.required ? '' : '?'}: ${typeof i.type === 'string' ? i.type : 'any'};${i.description ? ` // ${i.description.replace(/\n*/g, '')}` : ''}`)
	})
	let hasGroup = path.length > 0 && params.length > 0
	let nameList = [name]
	if (hasGroup) {
		nameList.push(name + 'Group')
	}
	let code = `
/**
 * @description ${apiName} buildReqGetOrPostTyped
 * @url ${url}
 */
export interface ${name} {${typeCode.length ? `\n\t${typeCode.join('\n\t')}` : ''}
}`.trim()
	if (hasGroup) {
		code += `
/**
 * @description ${apiName} buildReqGetOrPostTyped
 * @url ${url}
 */
export interface ${name}Group {
	path: {\n\t\t${pathCode.join('\n\t\t')}\n\t};
	params: ${name};
}
		`.trim()
	}
	return {
		name: nameList, code
	}
}

export function buildReqFileTyped(props) {
	const { url, name, apiName, fileProps } = props
	const typeCode = []
	if (fileProps.length === 0) {
		return {
			name: [], code: ''
		}
	}
	fileProps.forEach((i) => {
		typeCode.push(`${i.name}: string;${i.description ? ` // ${i.description.replace(/\n+/g, '')}` : ''}`)
	})
	return {
		name: [`${name[0]}FormData`], deleteName: name[0], code: `
/**
 * @description ${apiName} buildReqFileTyped
 * @url ${url}
 */
export interface ${name[0]}FormData extends FormData{
\tappend: (name: ${fileProps
		.map((i) => `'${i.name}'`)
		.join(' | ')}, value: string | Blob, fileName?: string) => void;
}`.trim()
	}
}

export function buildRes(config) {
	let data = config.response
	if (data.oldType === 'array') {
		const { code, valueCode, name, deleteName } = buildResArrayTyped({
			url: config.pureUrl, name: config.responseBaseName, apiName: config.apiName, params: data
		})
		return {
			responseName: name, deleteResponseName: deleteName, resCode: code, resValueCode: valueCode
		}
	} else if (data.type === 'any' || data.type === config.responseBaseName) {
		const { code, valueCode, name } = buildResAnyTyped({
			url: config.pureUrl, name: config.responseBaseName, apiName: config.apiName, params: data
		})
		return {
			responseName: name, resCode: code, resValueCode: valueCode
		}
	} else {
		// 基础类型，无需处理
		return {
			responseName: [config.responseBaseName], resCode: `
/**
 * @description ${config.apiName} buildRes
 * @url ${config.url}
 */
export type ${config.responseBaseName} = ${config.response.type}
`.trim(), resValueCode: `
/**
 * @description ${config.apiName} buildRes
 * @url ${config.url}
 */
export const ${config.responseBaseName}Value = ${getValueByType(config.response.type)}
`.trim()
		}
	}
}

export function buildResArrayTyped(props) {
	const { url, name, apiName, params } = props
	const typeCode = []
	const valueCode = []
	if (params.items.properties) {
		Object.keys(params.items.properties)
		      .sort((a, b) => a.localeCompare(b))
		      .forEach((key) => {
			      let item = params.items.properties[key]
			      typeCode.push(`${key}: ${item.type};${item.description ? ` // ${item.description.replace(/\n+/g, '')}` : ''}`)
			      valueCode.push(`${key}: ${getValueByType(item.type)},${item.description ? ` // ${item.description.replace(/\n+/g, '')}` : ''}`)
		      })
	}
	return {
		name: [`${name}Item`], deleteName: name, code: `
/**
 * @description ${apiName} buildResArrayTyped
 * @url ${url}
 */
export type ${name}Item = {${typeCode.length ? `\n\t${typeCode.join('\n\t')}` : ''}
}`.trim(), valueCode: `
/**
 * @description ${apiName} buildResArrayTyped
 * @url ${url}
 */
export const ${name}ItemValue = {${valueCode.length ? `\n\t${valueCode.join('\n\t')}` : ''}
}`.trim()
	}
}

export function buildResAnyTyped(props) {
	const { url, name, apiName, params } = props
	const typeCode = []
	const valueCode = []
	let nameList = [name]
	let subCode = []
	let subValueCode = []
	if (params.items?.properties) {
		Object.keys(params.items.properties)
		      .sort((a, b) => a.localeCompare(b))
		      .forEach((key) => {
			      let item = params.items.properties[key]
			      typeCode.push(`${key}: ${item.type};${item.description ? ` // ${item.description.replace(/\n+/g, '')}` : ''}`)
			      valueCode.push(`${key}: ${getValueByType(item.type)},${item.description ? ` // ${item.description.replace(/\n+/g, '')}` : ''}`)
		      })
	} else {
		if (params.properties && Object.keys(params.properties).length) {
			Object.keys(params.properties)
			      .sort((a, b) => a.localeCompare(b))
			      .forEach((key) => {
				      let item = params.properties[key]
				      if (item.type) {
					      let dataTpl = item.type
					      if (Array.isArray(dataTpl)) {
						      dataTpl = dataTpl[0]
						      if (Array.isArray(dataTpl)) {
							      dataTpl = 'any[]'
						      }
					      }
					      if (typeof dataTpl === 'object') {
						      let subName = name + upperFirstCase(key)
						      let arrType = Array.isArray(item.type) ? '[]' : ''
						      typeCode.push(`${key}: ${subName}${arrType};${item.description ? ` // ${item.description.replace(/\n+/g, '')}` : ''}`)
						      valueCode.push(`${key}: ${getValueByType(subName + arrType)},${item.description ? ` // ${item.description.replace(/\n+/g, '')}` : ''}`)
						      let subTypeCode = []
						      let subValueTypeCode = []
						      Object.keys(dataTpl)
						            .sort((a, b) => a.localeCompare(b))
						            .forEach((subKey) => {
							            let typeValue = typeof dataTpl[subKey] === 'string' ? dataTpl[subKey] : Array.isArray(dataTpl[subKey]) ? 'any[]' : 'any'
							            subTypeCode.push(`${subKey}: ${typeValue};`)
							            subValueTypeCode.push(`${subKey}: ${getValueByType(typeValue)},`)
						            })
						      nameList.push(subName)
						      subCode.push(`
/**
 * @description ${apiName}  buildResAnyTyped
 * @url ${url}
 */
export type ${subName} = {${subTypeCode.length ? `\n\t${subTypeCode.join('\n\t')}` : ''}
}`.trim())
						      subValueCode.push(`
/**
 * @description ${apiName}  buildResAnyTyped
 * @url ${url}
 */
export const ${subName}Value = {${subValueTypeCode.length ? `\n\t${subValueTypeCode.join('\n\t')}` : ''}
}`.trim())
					      } else {
						      typeCode.push(`${key}: ${dataTpl};${item.description ? ` //${item.description.replace(/\n+/g, '')}` : ''}`)
						      valueCode.push(`${key}: ${getValueByType(dataTpl)},${item.description ? ` //${item.description.replace(/\n+/g, '')}` : ''}`)
					      }
				      }
			      })
		}
	}
	// if (url === '/admin/investor/record/basic') {
	// 	console.log(params.properties?.twoFaStatus?.type)
	// 	console.log(typeCode)
	// }
	return {
		name: nameList, code: `${subCode.join('\n')}
/**
 * @description ${apiName} buildResAnyTyped
 * @url ${url}
 */
export type ${name} = {${typeCode.length ? `\n\t${typeCode.join('\n\t')}` : ''}
}`.trim(), valueCode: `${subValueCode.join('\n')}
/**
 * @description ${apiName} buildResAnyTyped
 * @url ${url}
 */
export const ${name}Value = {${valueCode.length ? `\n\t${valueCode.join('\n\t')}` : ''}
}`.trim()
	}
}

export function translateType(config, oldReqData, oldResData) {
	config = JSON.parse(JSON.stringify(config))
	config.request.params = config.request.params.map((sub) => {
		sub.type = typeTransform[sub.type] || sub.type
		// if (/\d/.test(sub.description)) {
		// 	let enumList = sub.description.match(/\d+/g)
		// 	if (enumList.length > 1) {
		// 		if (sub.type === 'number') {
		// 			sub.type = enumList.join(' | ')
		// 		} else if (sub.type === 'string') {
		// 			sub.type = enumList.map(i => `'${i}'`).join(' | ')
		// 		}
		// 	}
		// }
		return sub
	})
	config.request.path = config.request.path.map((sub) => {
		sub.type = typeTransform[sub.type] || sub.type
		// if (/\d/.test(sub.description)) {
		// 	let enumList = sub.description.match(/\d+/g)
		// 	if (enumList.length > 1) {
		// 		if (sub.type === 'number') {
		// 			sub.type = enumList.join(' | ')
		// 		} else if (sub.type === 'string') {
		// 			sub.type = enumList.map(i => `'${i}'`).join(' | ')
		// 		}
		// 	}
		// }
		return sub
	})
	config.request.query = config.request.query.map((sub) => {
		sub.type = typeTransform[sub.type] || sub.type
		// if (/\d/.test(sub.description)) {
		// 	let enumList = sub.description.match(/\d+/g)
		// 	if (enumList.length > 1) {
		// 		if (sub.type === 'number') {
		// 			sub.type = enumList.join(' | ')
		// 		} else if (sub.type === 'string') {
		// 			sub.type = enumList.map(i => `'${i}'`).join(' | ')
		// 		}
		// 	}
		// }
		return sub
	})
	config.request.params = config.request.params.map((item, index) => {
		config.request.params[index].oldType = item.type
		delete config.request.params[index].format
		delete item['x-tmp-pending-properties']
		if (item.type === 'array') {
			// 如果是数组类型，判断子项
			let newType = typeTransform[item.items?.type]
			if (newType) {
				config.request.params[index].type = newType
			} else {
				config.request.params[index].type = item.items?.type
			}
			// delete item.items;
		} else {
			let type = typeTransform[item.type] || item.type
			// if (/\d/.test(item.description)) {
			// 	let enumList = item.description.match(/\d+/g)
			// 	if (enumList.length > 1) {
			// 		if (type === 'number') {
			// 			type = enumList.join(' | ')
			// 		} else if (type === 'string') {
			// 			type = enumList.map(i => `'${i}'`).join(' | ')
			// 		}
			// 	}
			// }
			config.request.params[index].type = type
		}
		return config.request.params[index]
	})
	if (oldReqData) {
		if (oldReqData.params?.length) {
			oldReqData.params.forEach((item) => {
				let has = config.request.params.find((i) => i.name === item.name)
				if (!has) {
					config.request.params.push(item)
				}
			})
		}
		if (oldReqData.query?.length) {
			oldReqData.query.forEach((item) => {
				let has = config.request.query.find((i) => i.name === item.name)
				if (!has) {
					config.request.query.push(item)
				}
			})
		}
		if (oldReqData.path?.length) {
			oldReqData.path.forEach((item) => {
				let has = config.request.path.find((i) => i.name === item.name)
				if (!has) {
					config.request.path.push(item)
				}
			})
		}
	}
	// 优先处理真实的返回值结构
	if (oldResData) {
		// 处理数组
		if (Array.isArray(oldResData)) {
			let properties = {}
			Object.keys(oldResData[0])
			      .sort((a, b) => a.localeCompare(b))
			      .forEach((key) => {
				      let value = oldResData[0][key]
				      let type = typeof value === 'string' ? value : `any${Array.isArray(value) ? '[]' : ''}`
				      properties[key] = { type }
			      })
			config.response = {
				type: config.responseBaseName + 'Item', oldType: 'array', items: {
					type: 'object', properties
				}
			}
		} else if (oldResData && typeof oldResData === 'object') {
			// 处理对象
			let properties = {}
			Object.keys(oldResData)
			      .sort((a, b) => a.localeCompare(b))
			      .forEach((key) => {
				      let value = oldResData[key]
				      properties[key] = { type: value }
			      })
			config.response = {
				type: config.responseBaseName, oldType: 'object', properties
			}
		} else {
			// console.log('处理基本类型', config.pureUrl, oldReqData)
			// 处理基本类型
			config.response = {
				type: oldResData, oldType: oldResData
			}
		}
	} else {
		let newRes = JSON.parse(JSON.stringify(config.response))
		newRes.oldType = newRes.type
		if (newRes.type === 'array') {
			let newType = typeTransform[newRes.items?.type]
			if (newType && newType !== 'any') {
				// 基础数组类型
				newRes.type = newType
			} else {
				// 自定义数组类型
				newRes.type = config.responseBaseName + 'Item'
				let sub = newRes.items.properties
				for (let key in sub) {
					let subItem = sub[key]
					delete sub[key].format
					sub[key].oldType = subItem.type
					if (subItem.type === 'array') {
						sub[key].type = typeTransform[subItem.type] || subItem.type
					} else {
						if (subItem.type) {
							sub[key].type = typeTransform[subItem.type] || subItem.type
						} else {
							sub[key].type = 'any'
						}
					}
				}
			}
		} else {
			let newType = typeTransform[newRes.type]
			if (newType && newType !== 'any') {
				newRes.type = newType
			} else {
				if (newRes.properties && Object.keys(newRes.properties).length) {
					newRes.type = config.responseBaseName
					for (let key in newRes.properties) {
						let item = newRes.properties[key]
						if (item.$ref) {
							newRes.properties[key].type = 'any'
						}
					}
				} else {
					newRes.type = 'any'
				}
			}
		}
		config.response = newRes
		for (let key in config.response.properties) {
			let item = config.response.properties[key]
			if (item.type) {
				config.response.properties[key].type = typeTransform[item.type] || item.type
			}
		}
	}
	return config
}

export function fnCode(dataList, methodImport, requestImport, responseImport) {
	const codeString = dataList
	.filter((i) => !!i.fnCode)
	.map((i) => i.fnCode)
	.join('\n')
	return `${methodImport}
${outFileType === 'ts' ? requestImport.replace(/\$/, `\n\t${dataList
	.filter((i) => {
		let list = i.requestName
		list = list.filter((s) => new RegExp('[^a-zA-Z]' + s + '[^a-zA-Z]').test(codeString))
		return list.length > 0
	})
	.map((i) => {
		return i.requestName.sort((a, b) => a.localeCompare(b)).join(',\n\t')
	})
	.sort((a, b) => a.localeCompare(b))
	.join(',\n\t')},\n`) : ''}
${outFileType === 'ts' ? responseImport.replace(/\$/, `\n\t${dataList
	.filter((i) => {
		let list = i.responseName
		list = list.filter((s) => new RegExp('[^a-zA-Z]' + s + '[^a-zA-Z]').test(codeString))
		return list.length > 0
	})
	.map((i) => {
		return i.responseName.sort((a, b) => a.localeCompare(b)).join(',\n\t')
	})
	.sort((a, b) => a.localeCompare(b))
	.join(',\n\t')},\n`) : ''}
${codeString}
`
}

export function reqCode(dataList) {
	return `
${dataList
	.filter((i) => !!i.reqCode)
	.map((i) => i.reqCode)
	.join('\n\n')}
export default {
    ${dataList
	.filter((i) => i.requestName?.length > 0)
	.map((i) => {
		return i.requestName.sort((a, b) => a.localeCompare(b)).join(',\n\t')
	})
	.sort((a, b) => a.localeCompare(b))
	.join(',\n\t')},
}
`
}

export function resCode(dataList) {
	return `\n${dataList
	.filter((i) => !!i.resCode)
	.map((i) => i.resCode)
	.join('\n\n')}
export default {
\t${dataList
	.filter((i) => i.responseName?.length > 0)
	.map((i) => {
		return i.responseName.sort((a, b) => a.localeCompare(b)).join(',\n\t')
	})
	.sort((a, b) => a.localeCompare(b))
	.join(',\n\t')},
}
`
}

export function resValueCode(dataList) {
	return `\n${dataList
	.filter((i) => !!i.resValueCode)
	.map((i) => i.resValueCode)
	.join('\n\n')}
export default {
\t${dataList
	.filter((i) => i.responseValueName?.length > 0)
	.map((i) => {
		return i.responseValueName.sort((a, b) => a.localeCompare(b)).join(',\n\t')
	})
	.sort((a, b) => a.localeCompare(b))
	.join(',\n\t')},
}
`
}

export function deepMerge(oldRes, newRes, isFirst = false) {
	let baseType = ['string', 'number', 'boolean', 'any', 'any[]', 'any[][]']
	if (oldRes === null || oldRes === undefined || newRes === null) {
		return oldRes || newRes
	} else if (typeof oldRes === 'string') {
		if (baseType.includes(oldRes) && typeof oldRes === typeof newRes) {
			return oldRes
		} else {
			if (JSON.stringify(oldRes) !== JSON.stringify(newRes)) {
				console.log(`整体结构变化: 由【${oldRes}】变成了【${newRes}】`)
			}
			return newRes
		}
	} else {
		if ((Array.isArray(oldRes) && Array.isArray(newRes)) || (!Array.isArray(oldRes) && !Array.isArray(newRes))) {
			if (Array.isArray(newRes)) {
				//合并数组
				return [deepMerge(oldRes[0], newRes[0])]
			} else {
				let obj = {}
				if (isFirst) {
					if (Object.keys(newRes).length === 0) {
						return oldRes
					}
				}
				// 合并对象
				for (let key in oldRes) {
					let oldV = oldRes[key]
					let newV = newRes[key]
					if (newV === undefined) {
						console.log(`删除了字段: ${key}:${oldV}`)
					} else {
						obj[key] = deepMerge(oldV, newV)
					}
				}
				for (let key in newRes) {
					let oldV = oldRes[key]
					let newV = newRes[key]
					if (oldV === undefined) {
						console.log(`新增了字段: ${key}:${newV}`)
						obj[key] = newV
					} else {
						obj[key] = deepMerge(oldV, newV)
					}
				}
				return obj
			}
		} else {
			if (!baseType.includes(newRes)) {
				if (JSON.stringify(oldRes) !== JSON.stringify(newRes)) {
					console.log(`整体结构变化: 由【${oldRes}】变成了【${newRes}】`)
				}
				return newRes
			} else {
				return oldRes
			}
		}
	}
}

export function viteProxy(proxy) {
	console.log('proxy')
	proxy.on('error', (e) => {
		console.log('proxyError , 代理失败：服务器错误')
		console.log(JSON.stringify(e, null, 4))
	})
	proxy.on('proxyRes', (proxyRes, req, res) => {
		let encodingType = proxyRes.headers['content-encoding']
		const body = []
		proxyRes.on('data', (chunk) => {
			body.push(chunk)
		})
		proxyRes.on('end', () => {
			let buff = Buffer.concat(body)
			buffDecompress(req.url, encodingType, buff, (json) => {
				if (json && typeof json === 'object') {
					if (json.code === 200 && json.data !== undefined && json.data !== null) {
						// 处理url
						let urlPath = req.url.split('?')[0]
						urlPath = urlPath
						.replace(VITE_APP_API_PREFIX, '')
						.replace(/(\/\d+)$/g, '/{x}')
						let key = req.method.toLowerCase() + '_' + urlPath
						// 每次都重新读取文件
						const docFile = fs.existsSync(path.resolve(tempDir + 'doc.json')) ? JSON.parse(fs.readFileSync(path.resolve(tempDir + 'doc.json'), { encoding: 'utf-8' }) || '[]') : []
						const resFile = fs.existsSync(path.resolve(tempDir + 'response.body.json')) ? JSON.parse(fs.readFileSync(path.resolve(tempDir + 'response.body.json'), {
							encoding: 'utf-8'
						}) || '{}') : {}
						const dataFile = fs.existsSync(path.resolve(tempDir + 'data.json')) ? JSON.parse(fs.readFileSync(path.resolve(tempDir + 'data.json'), {
							encoding: 'utf-8'
						}) || '{}') : {}
						let index = -1
						docFile.forEach((i, idx) => {
							if (i.bodyKey === key) {
								index = idx
							}
						})
						if (index === -1) {
							index = docFile.findIndex((i) => {
								const reg = new RegExp(i.bodyKey.replace('{x}', '[^\\/]+$'))
								if (reg.test(key)) {
									key = i.bodyKey
									return true
								}
							})
						}
						console.log(urlPath, key)
						if (dataFile[key]) {
							if (Array.isArray(json.data) && json.data.length) {
								dataFile[key] = json.data
							} else if (json.data !== null && typeof json.data === 'object' && Object.keys(json.data).length) {
								dataFile[key] = json.data
							} else if (typeof json.data === 'boolean' || typeof json.data === 'number') {
								dataFile[key] = json.data
							} else if (typeof json.data === 'string' && json.data !== '') {
								dataFile[key] = json.data
							}
						} else {
							dataFile[key] = json.data
						}
						dataFile[key] = json.data
						let newData = {}
						Object.keys(dataFile)
						      .sort((a, b) => a.localeCompare(b))
						      .forEach((i) => {
							      newData[i] = dataFile[i]
						      })
						fs.writeFileSync(path.resolve(tempDir + 'data.json'), JSON.stringify(newData, null, 4), { encoding: 'utf-8' })
						// 找到对应的原始数据结构
						const oldRes = resFile[key]
						// 处理新的数据结构
						const newRes = clearResponseData(json.data)
						let newObj = deepMerge(oldRes, newRes, true)
						// 判断更新条件
						if (JSON.stringify(oldRes) !== JSON.stringify(newObj)) {
							console.log(urlPath, '返回值类型：', Array.isArray(newRes) ? 'array' : newRes === null ? 'null' : typeof newRes === 'string' ? newRes : typeof newRes)
							delete resFile[urlPath]
							resFile[key] = newObj
							fs.writeFileSync(tempDir + 'response.body.json', JSON.stringify(resFile, null, 4), {
								encoding: 'utf-8'
							})
							if (index > -1) {
								const oldDocItem = docFile[index]
								// 处理前后端类型映射
								let newDocItem = translateType(oldDocItem, null, newRes)
								newDocItem = {
									...newDocItem, ...buildRes(newDocItem)
								}
								docFile[index] = newDocItem
								fs.writeFileSync(tempDir + 'doc.json', JSON.stringify(docFile, null, 4), {
									encoding: 'utf-8'
								})
								fs.writeFileSync(responseOutDir, (() => {
									return `\n${docFile
									.filter((i) => !!i.resCode)
									.map((i) => i.resCode)
									.join('\n\n')}\n\nexport default {\n\t${docFile
									.filter((i) => i.responseName?.length > 0)
									.map((i) => i.responseName.join(',\n\t'))
									.join(',\n\t')},\n}\n`
								})(), { encoding: 'utf-8' })
								fs.writeFileSync(responseValueOutDir, (() => {
									return `\n${docFile
									.filter((i) => !!i.resValueCode)
									.map((i) => i.resValueCode)
									.join('\n\n')}\n\nexport default {\n\t${docFile
									.filter((i) => i.responseValueName?.length > 0)
									.map((i) => i.responseValueName.join(',\n\t'))
									.join(',\n\t')},\n}\n`
								})(), { encoding: 'utf-8' })
								// 重新生成所有接口信息
								const apiList = fs.existsSync(path.resolve(tempDir + 'details-old.json')) ? JSON.parse(fs.readFileSync(path.resolve(tempDir + 'details-old.json'), { encoding: 'utf-8' }) || '{}') : []
								const schemas = fs.existsSync(path.resolve(tempDir + 'schemas-old.json')) ? JSON.parse(fs.readFileSync(path.resolve(tempDir + 'schemas-old.json'), { encoding: 'utf-8' }) || '{}') : []
								build(apiList, schemas)
							}
						}
					}
				}
			})
			let type = proxyRes.rawHeaders.join(':').match(/content-type:([^;:,\s"']+)/i)?.[1]
			res.setHeader('Content-Type', type || 'application/json')
			res.end(buff)
		})
	})
}

function buffDecompress(url, encodingType, buff, callback) {
	if (encodingType === 'gzip' || encodingType === 'br') {
		zlib[encodingType === 'gzip' ? 'unzip' : 'brotliDecompress'](buff, (err, bufferDecompressed) => {
			if (!err) {
				const result = bufferDecompressed.toString()
				try {
					const json = JSON.parse(result)
					callback(json)
				} catch (e) {
					console.log(result)
					console.log('数据格式化时出现错误：', url)
					console.log(e)
				}
			} else {
				console.log(encodingType, '解压失败', url)
				console.log(err)
			}
		})
	} else {
		try {
			const json = JSON.parse(buff.toString('utf-8'))
			callback(json)
		} catch (e) {
			callback(buff.toString('utf-8'))
		}
	}
}

function getValueByType(type) {
	if (type.includes('[]')) {
		return '[]'
	} else if (type.includes('string')) {
		return '\'\''
	} else if (type.includes('number')) {
		return '-1'
	} else if (type.includes('boolean')) {
		return 'false'
	} else {
		return 'undefined'
	}
}

